package andrei.mower2.business;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import andrei.mower2.business.Mower.Command;
import andrei.mower2.business.exception.InvalidLawnDimensionsException;
import andrei.mower2.business.exception.PositionOutsideLawnException;
import andrei.mower2.business.output.MowerDriveResultOutputter;
import andrei.mower2.io.MowerInputListener;
import andrei.mower2.io.MowerInputReader;
import andrei.mower2.io.exceptions.UnparsableCommandException;
import andrei.mower2.io.exceptions.UnparsableMowerStartVectorException;
import andrei.mower2.main.i18n.MessageUtils;

public class MowerDriver {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(MowerDriver.class);
	
	private MowerInputReader mowerInputReader;
	private MowerDriveResultOutputter[] mowerDriveResultOutputters;
	private MowersDrivingContext drivingContext;
	
	private boolean isMowersEnd, isCurrentMowerCommandsEnd;
	private Mower currentMower;
		
	public MowerDriver(MowerInputReader mowerInputReader, MowerDriveResultOutputter... resultOutputters) {
		this.mowerInputReader = mowerInputReader;
		this.mowerDriveResultOutputters = resultOutputters;
	}

	public void driveMowers() {
		initMowerInputListener();
		initDrivingContext();		
		commandMowers();
	}
	
	private void initMowerInputListener() {
		mowerInputReader.addListener(new MowerFileInputListener());
	}
	
	private void initDrivingContext() {
		Position lawnUpperRightCorner = mowerInputReader.getLawnCorner();
		if(lawnUpperRightCorner.x <= 0 || lawnUpperRightCorner.y <= 0) {
			throw new InvalidLawnDimensionsException(MessageUtils.getMessage("error.business.lawnDimensions", lawnUpperRightCorner.x, lawnUpperRightCorner.y));
		}
		drivingContext = new MowersDrivingContext(lawnUpperRightCorner.x + 1 , lawnUpperRightCorner.y + 1);
	}
	
	private void commandMowers() {
		try {
			processNextMower();			
			while(!isMowersEnd) {
				processNextMower();
			}
		} finally {	
			closeResultOutputters();
		}
	}
	
	private void processNextMower() {
		isCurrentMowerCommandsEnd = false;
		try {
			MowerVector mowerStartVector = mowerInputReader.nextStartVector();
			if(isMowersEnd) {
				return;
			}
			placeMower(mowerStartVector);
		} catch (UnparsableMowerStartVectorException | PositionOutsideLawnException e) {
			LOGGER.warn(e.getMessage());
			return;
		}
		
		processNextCommand();		
		while(!isCurrentMowerCommandsEnd) {
			processNextCommand();		
		}
		processDriveResults();
	}
	
	private void processNextCommand() {
		try {
			Command command = mowerInputReader.nextCommand();
			if(isCurrentMowerCommandsEnd) {
				return;
			}
			commandMower(command);
		} catch (UnparsableCommandException | PositionOutsideLawnException e) {
			LOGGER.warn(e.getMessage());
		}
	}
	
	private void placeMower(MowerVector mowerStartVector) throws PositionOutsideLawnException {
		long mowerIndex = currentMower == null ? 0 : currentMower.getMowerIndex() + 1;
		currentMower = new Mower(mowerIndex, drivingContext);
		currentMower.placeMower(mowerStartVector);
	}
	
	private void commandMower(Command command) throws PositionOutsideLawnException {
		currentMower.execute(command);
	}
	
	private void processDriveResults() {
		if(mowerDriveResultOutputters == null) {
			return;
		}
		MowerDriveResult driveResult = generateDriveResult();
		for(MowerDriveResultOutputter outputter:mowerDriveResultOutputters) {
			outputter.output(driveResult);
		}
	}
	
	private MowerDriveResult generateDriveResult() {
		return new MowerDriveResult(currentMower.getMowerIndex(), currentMower.getCurrentVector());
	}
	
	private void closeResultOutputters() {
		if(mowerDriveResultOutputters != null) {
			for(MowerDriveResultOutputter resultOutputter:mowerDriveResultOutputters) {
				try {
					resultOutputter.close();
				} catch(IOException e) {
					LOGGER.error(e.getMessage(), e);
				}
			}
		}
	}
	
	private class MowerFileInputListener implements MowerInputListener {
		
		@Override
		public void onCurrentMowerCommandsEnd() {
			MowerDriver.this.isCurrentMowerCommandsEnd = true;
		}

		@Override
		public void onMowersEnded() {
			MowerDriver.this.isMowersEnd = true;
		}
		
	}
}
